home *** CD-ROM | disk | FTP | other *** search
/ Tech Arsenal 1 / Tech Arsenal (Arsenal Computer).ISO / tek-02 / tpkbd10.zip / KEYBOARD.DOC < prev    next >
Text File  |  1991-07-08  |  30KB  |  576 lines

  1.  
  2.  
  3.  
  4.  
  5.  
  6.                             A KEYBOARD INPUT UTILITY
  7.         
  8.         
  9.         If you are like me, you often write programs that use "special" 
  10.         keys like the arrow keys, function keys, etc., but the way these 
  11.         keys are implemented seems frustrating; first you have to read a 
  12.         null character, then do another read, and you have keep track of 
  13.         what code means what.  This detracts from the readability of the 
  14.         program and disrupts your flow of thought.  Or maybe you'd like 
  15.         to read numbers from the keyboard but not have to worry about 
  16.         trapping errors in input to avoid a fatal "Invalid numeric 
  17.         format" error or check to see if the number typed is in the range 
  18.         you want it.  Or maybe you'd like to have a better input routine 
  19.         than ReadLn to input a string.  This unit tackles all those 
  20.         problems, and a few others to enable you to write programs that 
  21.         contain clean, good-looking, error-free input.   
  22.         
  23.         
  24.                              SINGLE CHARACTER INPUT   
  25.         
  26.         
  27.         ReadKey is sometimes OK for character input, but when it comes to 
  28.         "special" keys that aren't normal ASCII characters, it's a real 
  29.         pain.  That is where a function in this unit called GetKey comes
  30.         in handy.  GetKey's calling syntax is the same as ReadKey's, and 
  31.         it returns a character like ReadKey does.  However, GetKey has 
  32.         some improvements over ReadKey:  
  33.         
  34.            - The key pressed can be ENTIRELY determined from
  35.              the character returned by the function.  There is no 
  36.              need to call the function again for special keys.  
  37.            - Special keys can be determined by comparing them 
  38.              against either constants or simple expressions, rather 
  39.              than remembering meaningless codes.  
  40.            - If the user presses a special key when your 
  41.              program expects regular character input, the extra 
  42.              keystroke that ReadKey would normally hold after the 
  43.              null character is not there with GetKey, eliminating 
  44.              the danger of misinterpreting an extra keystroke, so 
  45.              you should use GetKey in place of ALL calls to ReadKey, 
  46.              unless you make special provisions to avoid 
  47.              misinterpreting the extra keystroke.   
  48.         
  49.         If the character returned by GetKey is less than or equal to
  50.         #127, it is not a special character and should be taken at face  
  51.         value.  (Note: If GetKey returns a #0, it means the user entered 
  52.         a "null" character, not that there is another character waiting
  53.         to be  read.  A null can be entered by pressing the Control key
  54.         along  with the "@" key.)  However, if the character returned is
  55.         #128 or  greater, it means that the user pressed a special key
  56.         and that the  character should be interpreted as follows:  For
  57.         all special keys except Alt plus a letter, there are constants
  58.         that you can match against the character returned to determine
  59.         the key pressed. Here is a list of these constants, along with
  60.         the keys that generate them:
  61.  
  62.  
  63.  
  64.  
  65.  
  66.  
  67.              F1, F2, ... , F10 - one of the ten function keys.
  68.              ShiftF1, ShiftF2, ... , ShiftF10 - one of the
  69.                ten function keys together with the Shift key.
  70.              CntlF1, CntlF2, ... , CntlF10 - one of the ten
  71.                function keys together with the Control key.
  72.              AltF1, AltF2, ... , AltF10 - one of the ten
  73.                function keys together with the Alt key.
  74.              Alt0, Alt1, ... , Alt9 - the Alt key together with
  75.                one of the number keys from the top row of the
  76.                keyboard.
  77.              UpArrow, DownArrow, LeftArrow, RightArrow - one
  78.                of the directional arrow keys.
  79.              CntlLeftArrow, CntlRightArrow - the Control key
  80.                together with the left or right arrow key.
  81.                Normally, pressing the Control key together with the
  82.                up or down arrow key does not generate a character.
  83.              Home, End_, PgUp, PgDn, Ins, Del - the corresponding
  84.                key from the numeric keypad.  Note that the constant
  85.                for the "End" key ends with an underscore to avoid
  86.                conflict with the reserved word "end" in Turbo
  87.                Pascal.
  88.              CntlHome, CntlEnd, CntlPgUp, CntlPgDn - the
  89.                Control key together with the Home, End, PgUp, or
  90.                PgDn key.  The Control key together with the Ins or
  91.                Del may or may not produce a keystroke.  If it does,
  92.                it produces the same result as the Ins or Del key
  93.                without the Control key.
  94.              AltMinus, AltEqual - the Alt Key together with the
  95.                "-" or "=" key from the center section of the
  96.                keyboard.  The key from the far right side of the
  97.                keyboard will not produce a character when pressed
  98.                together with the Alt key.
  99.  
  100.         NOTE:  Support for the F11 and F12 keys is not included because
  101.         reading these keys requires calling a special "Read extended
  102.         keyboard" function, which can lock up or cause other problems
  103.         with computers that do not have BIOS support for reading the
  104.         extended keyboard.
  105.  
  106.         Here is a short program to demonstrate the GetKey function.  It
  107.         simply moves the cursor around on the screen as the user presses
  108.         the arrow keys.   The program will continue until you press the
  109.         F10 key:
  110.  
  111.         Program MoveCursor;
  112.  
  113.         uses Keyboard;
  114.  
  115.         const
  116.          XMax=80; {Change these if your screen}
  117.          YMax=25; {size is different from 80x25}
  118.  
  119.         var
  120.          X,Y:Integer;
  121.  
  122.  
  123.  
  124.  
  125.  
  126.          Ch:Char;
  127.  
  128.         begin
  129.          X:=WhereX; Y:=WhereY; {Read current location}
  130.          repeat
  131.           Ch:=GetKey;
  132.           case Ch of
  133.            LeftArrow : if X>1 then dec(X); {Left arrow pressed}
  134.            RightArrow : if X<Xmax then inc(X); {Right arrow pressed}
  135.            UpArrow : if Y>1 then dec(Y); {Up arrow pressed} 
  136.            DownArrow : if Y<Ymax then inc(Y); {Down arrow pressed} 
  137.           end; 
  138.           GotoXY(X,Y); {Move to the new location} 
  139.          until Ch=F10; {Check for the F10 key} 
  140.         end.   
  141.         
  142.         The comments are not necessary, since this program is self-
  143.         documenting because of the named constants, where a program that 
  144.         worked with ReadKey and raw character codes would need extensive 
  145.         commenting to explain itself.  Note that the routines WhereX, 
  146.         WhereY, and GotoXY normally require a "Uses CRT" statement 
  147.         because they reside in the CRT unit.  However, the CRT unit can 
  148.         cause problems in modes where the screen width is greater than 80 
  149.         columns.  Since this unit used these routines, they have been 
  150.         implemented with BIOS calls in order to keep the unit compatible 
  151.         with all video modes.  Since they were already written, they have 
  152.         simply been put in the interface part of the unit so other 
  153.         programs can use them too.  If you still want to use the routines
  154.         in the CRT unit, you can either delete the {$DEFINE NOCRT} line
  155.         from the interface part of the unit (if you have the source 
  156.         code), or you can use the fully qualified names (e.g., 
  157.         crt.wherex, crt.gotoxy, etc.) in your program.
  158.         
  159.         A NOTE ON CRT COMPATIBILITY (the KeyPressed function):  The only 
  160.         routine in the Keyboard unit that is known to have a somewhat 
  161.         different effect than the corresponding routine in the CRT unit 
  162.         is the KeyPressed function.  This function, in addition to 
  163.         checking for keys pressed, also has the side effect that if it is 
  164.         called and a key is waiting and that key turns out to be a keypad 
  165.         '5' while in non-numeric mode, it will be silently removed from 
  166.         the buffer.  This is a necessary compromise, because these keys  
  167.         continue to be put in the buffer regardless of the value of the 
  168.         nonnumeric variable.  This would cause erroneous results, because 
  169.         getkey will ignore keypad '5' keys when in non-numeric mode, to 
  170.         be consistent with the way the keypad normally works, so 
  171.         keypressed would be returning TRUE when actually there was no key 
  172.         available.  It could not simply ignore the keystrokes, because 
  173.         they would still be there next time it was called, and it would 
  174.         never be able to return TRUE.  Eventually, the buffer could fill 
  175.         with 5's if they were not removed.  The only situation this 
  176.         causes is that if you call this routine with nonnumeric equal to
  177.         TRUE and there is a 5 in the buffer, then you set nonnumeric to
  178.         FALSE and call it again, the 5 will not be there any more, even 
  179.         though now it should be able to be read.  This seemed to be the 
  180.         best solution, although it is still somewhat imperfect.
  181.  
  182.  
  183.  
  184.  
  185.  
  186.         
  187.         Another possibility is that the Alt key has been pressed along 
  188.         with one of the letters A-Z.  Because the unit already has so 
  189.         many constants defined in it, it would be awkward to define 
  190.         constants for each key.  Instead, GetKey returns a character 
  191.         equal to the ASCII value of the uppercase version of the key 
  192.         pressed plus a constant amount.  The constant "Alt" has been 
  193.         defined for use in testing this condition.  The character 
  194.         returned will be equal to Chr(Ord(ASCII_value)+Alt).  To 
  195.         illustrate this, the following short program will continually 
  196.         read characters from the keyboard, and if the Alt key is pressed 
  197.         along with a letter, it will print the key that was pressed.  
  198.         Again, the program ends when you press the F10 key:  
  199.         
  200.         program AltDemo;  
  201.         
  202.         uses Keyboard;  
  203.         
  204.         const
  205.          A=ord('A');
  206.          Z=ord('Z');
  207.         
  208.         var Ch:Char;
  209.         
  210.         begin 
  211.          repeat 
  212.           Ch:=GetKey; 
  213.           if (Ch>=chr(Alt+A)) and (Ch<=chr(Alt+Z)) then 
  214.            writeln('You pressed Alt-',chr(ord(Ch)-Alt)); 
  215.          until Ch=F10; 
  216.         end.   
  217.         
  218.         Since it is possible to enter characters by holding down the Alt 
  219.         key and typing in the ASCII value of the character on the numeric 
  220.         keypad, it is possible for a character greater than #127 to be 
  221.         entered from the keyboard.  Since GetKey returns special 
  222.         characters as characters above #127, if your program does not 
  223.         detect this, it may misinterpret the character as a special 
  224.         character.  To correct for this, GetKey sets a predefined 
  225.         variable called AltTyped, which will show whether the character 
  226.         was entered this way.  If this variable is TRUE, the character 
  227.         was entered by typing it in on the numeric keypad.  If it is 
  228.         FALSE, the character was entered by normal keyboard input.  If
  229.         you want to handle these situations differently, or just think 
  230.         your user might do something strange, you probably will want to 
  231.         test this variable before you process any special keys.  Also, 
  232.         you are no doubt aware that the keys on the numeric keypad can be 
  233.         interpreted as either cursor control keys or as numbers, 
  234.         depending on the Shift and NumLock keys.  You may want to treat 
  235.         these as cursor control keys all the time.  For example, a 
  236.         drawing program in which the user draws by moving a cursor around 
  237.         on the screen probably would find it useless to treat these as 
  238.         number keys.  You can specify whether to treat these keys 
  239.         specially by setting a predefined variable called "NonNumeric" 
  240.         before calling GetKey.  When this variable is TRUE, GetKey treats 
  241.  
  242.  
  243.  
  244.  
  245.  
  246.         the keys as cursor control keys and returns appropriate values 
  247.         regardless of the condition of the Shift and NumLock keys.  When 
  248.         it is FALSE, GetKey treats these keys normally, returning numbers 
  249.         when the shift key is down or when NumLock is on.  This variable 
  250.         is initially FALSE by default.  
  251.         
  252.         
  253.                               NUMERIC/STRING INPUT   
  254.         
  255.         
  256.         This unit also contains powerful input routines that are a vast
  257.         improvement over Read and ReadLn.  Three of these input routines 
  258.         deal with numeric input, while two deal with string input.  Here 
  259.         are the headings for these five routines:
  260.         
  261.            procedure ReadNo(var Number:Word; LoBound,
  262.              HiBound:Word);
  263.            procedure ReadInt(var Number:Integer; LoBound, 
  264.              Hibound:Integer); 
  265.            procedure ReadReal(var Number:Real; LoBound, 
  266.              HiBound:Real; Decimals:Byte); 
  267.            procedure ReadStr(var S:String; MaxLen:Byte; 
  268.              CharsToExclude:CharSet); 
  269.            procedure EditStr(var S:String; MaxLen:Byte; 
  270.              CharsToExclude:CharSet);  
  271.  
  272.         CharSet is a type defined as set of Char.  Unlike ReadLn, these 
  273.         procedures do not skip to the next line when the user hits return 
  274.         at the end.  This is because sometimes you may want the cursor to 
  275.         stay on the same line after the input.  You can always add a 
  276.         blank WriteLn statement if you want to skip to the next line.  
  277.         ReadNo, ReadInt, and ReadReal input numbers.  ReadNo inputs 
  278.         positive (unsigned) integers, ReadInt inputs positive or negative 
  279.         (signed) integers, and  ReadReal inputs real numbers.  LoBound
  280.         and HiBound are the minimum and maximum values for the number to 
  281.         be input.  For ReadReal, the parameter Decimals is the minimum 
  282.         number of decimal places you want the user to be able to enter.  
  283.         The maximum width the user will be allowed to type in will be 
  284.         determined by the values passed for LoBound and HiBound (and 
  285.         Decimals, if appropriate).  For example, if you call ReadNo with 
  286.         LoBound=0 and HiBound=500, the user will be given a maximum width 
  287.         of three to type in, since no more than three digits will be 
  288.         possible.  If you call ReadReal with LoBound=0, HiBound=500, and 
  289.         Decimals=3, the user will be given a maximum width of 7 to type 
  290.         in (3 for the whole number part, 1 for the decimal point, and 3 
  291.         for the decimal part).  In each case, the input will be 
  292.         restricted to allow only characters appropriate for the type of 
  293.         number you are reading.  ReadNo allows only digits to be entered.  
  294.         ReadInt allows only digits and a possible negative sign as the 
  295.         first character.  ReadReal allows only digits, a decimal point, 
  296.         and a possible negative sign as the first character.  It will not 
  297.         allow more than one decimal point to be entered.  All three 
  298.         procedures will exit immediately without doing anything if the 
  299.         HiBound is less than the LoBound.  If the user does not enter 
  300.         anything and just hits return, the numeric variable is returned 
  301.  
  302.  
  303.  
  304.  
  305.  
  306.         unchanged, so if you want there to be some default value for 
  307.         these procedures to return, you should set the variable to that
  308.         value before calling the procedure.  If the number entered is out 
  309.         of the range specified, the procedure will beep and wait for 
  310.         another number to be input.  (One exception: if the default value 
  311.         passed is out of the range specified and the user hits return to 
  312.         accept the default value, it will allow it.  This will be shown 
  313.         to be a useful feature later.)  You should make the user aware of 
  314.         both the range expected and the default value.  If, for some 
  315.         reason, you don't want there to be any default value, you can 
  316.         take advantage of the fact that the cursor stays on the same line 
  317.         after input by first setting your numeric variable to an out-of-
  318.         range value and continually reading until the variable is in 
  319.         range.  Suppose you wanted to enter a number between 1 and 500 
  320.         but there was no good default value you could use.  The following 
  321.         two lines will make the user enter a value that is in range:
  322.         
  323.          Number:=1000; {Start with an out-of-range value} 
  324.          repeat ReadNo(Number,1,500) until Number<>1000;  
  325.         
  326.         .cp 2
  327.         If the user enters nothing and just hits return, the cursor will 
  328.         stay exactly where it is so you can immediately make another call 
  329.         to ReadNo.  The program would detect that the user did not enter 
  330.         anything because the default value (1000) would be returned, and
  331.         it would make the user enter something before continuing.
  332.         
  333.         ReadStr and EditStr input strings.  In each case, MaxLen is the 
  334.         maximum length you want to allow the string to be, and 
  335.         CharsToExclude is a set parameter that should contain any 
  336.         characters that you do not want to allow the user to enter.  For 
  337.         example, when prompting for a filename, you may want to exclude 
  338.         control characters (below #32), blank (' '--#32), and characters 
  339.         above a lowercase z. So, if you wanted to limit the name to 30 
  340.         characters and exclude the characters mentioned, you would call 
  341.         ReadStr or EditStr as follows:
  342.         
  343.         .cp 3
  344.           ReadStr(StringVar,30,[#0..#32,#123..#255]); 
  345.           - or - 
  346.           EditStr(StringVar,30,[#0..#32,#123..#255]);
  347.         
  348.         If you have a large range of characters you want to disallow but 
  349.         a small range you want to allow, you can take advantage of the 
  350.         set difference operator and pass the invalid characters as the 
  351.         set of all characters minus the set of valid characters.  For 
  352.         example, if you wanted to use ReadStr to read a single character 
  353.         command followed by a carriage return (i.e., a string of length
  354.         1), and your range of commands was a digit from '1' to '9' or 'Q' 
  355.         to quit, rather than figure all the characters that were invalid, 
  356.         you could make the following call:
  357.         
  358.           ReadStr(Command,1,[#0..#255]-['1'..'9','Q','q']);
  359.         
  360.         This says to disallow all characters except the digits 1-9 or a 
  361.  
  362.  
  363.  
  364.  
  365.  
  366.         capital or lowercase 'Q'. 
  367.         
  368.         ReadStr allows the user to start with a blank string and enter 
  369.         data.  It is somewhat similar to ReadLn, except for the extra 
  370.         parameters for formatting.  If the user enters a null (blank) 
  371.         string, the string variable passed is returned unchanged.  So, 
  372.         before calling ReadStr, you should set your string variable to 
  373.         whatever default value you want it to have if the user enters a 
  374.         null string.  If you don't want to set any default string in such 
  375.         a case but instead want a null string returned, you should set 
  376.         your string variable to a null string ('') before calling 
  377.         ReadStr.  EditStr allows the user to edit an already existing 
  378.         string.  You should set the string variable to whatever initial 
  379.         value you want it to have before you call EditStr.  When EditStr 
  380.         is called, the initial value will be displayed, and the user will 
  381.         get a chance to edit it.  The string will take on exactly
  382.         whatever value the user gives it--if the user changes it to a 
  383.         null string, it will be returned as a null string, regardless of 
  384.         what value it had when EditStr was called.  Both procedures allow 
  385.         the following editing features:
  386.         
  387.            ESC - Erase the entire string.
  388.            Backspace - Delete the character to the left of the 
  389.              cursor and move the cursor left one space.
  390.            Left Arrow - Move the cursor left one space.
  391.            Right Arrow - Move the cursor right one space.
  392.            Up Arrow - Move the cursor up one line.
  393.            Down Arrow - Move the cursor down one line.
  394.            Home - Move the cursor to the beginning of the string.
  395.            End - Move the cursor to the end of the string.
  396.            Ins - Toggle insert/typeover mode.
  397.            Del - Delete the character under the cursor.
  398.            Cntl-Home - Delete from the cursor to the beginning of 
  399.              the string.
  400.            Cntl-End - Delete from the cursor to the end of the 
  401.              string.
  402.  
  403.         These keys will only function if the cursor is in an appropriate 
  404.         position; you cannot backspace if you are at the beginning of the
  405.         string, you cannot go forward if you are at the end, you cannot 
  406.         move up or down if the string does not extend to the line below 
  407.         or above the cursor, etc.  The variable parameters for these 
  408.         procedures call for strings of 255 characters.  If you want to 
  409.         call one of them with a string variable of a different length, 
  410.         you will have to include a relaxed var-string directive ({$V-}) 
  411.         before the call.  This directive tells Turbo Pascal not to worry 
  412.         if the declared lengths of the formal and actual parameters 
  413.         differ when a string is used as a variable parameter, but it also 
  414.         means that if you try to put more characters into the string than 
  415.         will fit, you run the risk of overrunning the end of the string 
  416.         and overwriting data outside the string.  To avoid this, never 
  417.         call ReadStr or EditStr with a MaxLen greater than the declared 
  418.         length of the string.  If you follow this rule, you will never 
  419.         have anything to worry about.  
  420.         
  421.  
  422.  
  423.  
  424.  
  425.  
  426.         
  427.                           OTHER PROCEDURES IN THIS UNIT  
  428.         
  429.         
  430.         There are three procedures in this unit that can be used to 
  431.         affect the state of the CapsLock, NumLock, and ScrollLock flags.  
  432.         They are SetCapsLock, SetNumLock, and SetScrollLock, and each 
  433.         takes a single parameter.  The line SetCapsLock(On); will turn on 
  434.         CapsLock, while the line SetCapsLock(Off); will turn it off.  
  435.         "On" and "Off" are simple Boolean constants defined in the unit 
  436.         to be equal to TRUE and FALSE, respectively.  To test the current 
  437.         states of the flags, there are three Boolean functions, called 
  438.         GetCapsLock, GetNumLock, and GetScrollLock.  There is also a 
  439.         function to test the status of the Insert flag called GetInsert.  
  440.         You can test the status of the NumLock key with the following 
  441.         line:
  442.         
  443.           if GetNumLock=On then writeln('NumLock is on. ');  
  444.         
  445.         or, more compactly:
  446.         
  447.           if GetNumLock then writeln('NumLock is on. ');  
  448.         
  449.         Some programs simply set the status flags and do not return them 
  450.         to their previous states before the program ends.  This can be 
  451.         annoying.  It is always best to test the state of the flags 
  452.         before setting any of them, then return them to their previous 
  453.         states as soon as possible when you finish.  Except on AT-type 
  454.         computers, setting these keys affects only the PC's internal 
  455.         record of the state of the keys but does not affect the status 
  456.         lights on the keyboard that show whether the flags are on or off.  
  457.         Therefore, if you do not return the flags to their previous state 
  458.         when you finish, the lights on the keyboard may become out of 
  459.         synch with the actual states of the flags within the computer.  
  460.         If you can avoid it, it is best not to change these settings 
  461.         because the user can change the states of the flags between the 
  462.         time you save them and the time you restore them, which would 
  463.         still leave the keyboard lights out of synch with the PC.  
  464.         However, this is somewhat less likely so if you do find it 
  465.         necessary to change the flags, this is the best way to do it.
  466.         
  467.         There are five Boolean functions to test the current states of 
  468.         the Shift, Control, and Alt keys: LeftShiftDown, RightShiftDown, 
  469.         ShiftDown, ControlDown, and AltDown.  The names are self-
  470.         explanatory, except it should be noted that LeftShiftDown and 
  471.         RightShiftDown each return the state of one of the shift keys, 
  472.         where ShiftDown returns true if either shift key is down.
  473.         
  474.         There is a procedure called FlushBuffer, which clears any typed-
  475.         ahead characters from the input buffer.  Calling this procedure 
  476.         immediately before doing input can eliminate mistakes caused by 
  477.         the user either making a mistake trying to type ahead or 
  478.         accidentally hitting two keys at the same time, causing the 
  479.         second key to be read later.  DOS does this when you type "erase 
  480.         *.*" and you have to wait before typing "Y" or "N".  
  481.  
  482.  
  483.  
  484.  
  485.  
  486.         
  487.         .cp 2
  488.         There is a byte-valued function called ScreenWidth, which will 
  489.         return the number of columns on the screen.  This function was 
  490.         used by the input routines, so it was simply placed in the 
  491.         interface of the unit so other programs could use it also.
  492.         
  493.         Finally, there are two procedures dealing with the cursor called 
  494.         ChgCursor, which takes two byte-sized parameters to specify the 
  495.         starting and ending row (0 to 7) for the cursor; and GetCursor, 
  496.         which returns the current starting and ending row for the cursor.  
  497.         The typical thin cursor would be set by the line 
  498.         
  499.            ChgCursor(6,7); 
  500.         
  501.         which means to use the bottom two lines (6 and 7) in defining the 
  502.         cursor.  A block cursor can be set by the line 
  503.         
  504.            ChgCursor(0,7); 
  505.         
  506.         which means to use all the lines (0 through 7) in defining the 
  507.         cursor.  The cursor can be made invisible by the line 
  508.         
  509.            ChgCursor($20,0); 
  510.                 - or -
  511.            ChgCursor(32,0);  
  512.         
  513.         This procedure can be handy to vanish the cursor when waiting for 
  514.         single keypresses or to make "block" cursors in word processor- 
  515.         type programs to show that "insert mode" is on.  By calling 
  516.         GetCursor before calling ChgCursor, you can return the cursor to 
  517.         the state it was in before you changed it.
  518.         
  519.         
  520.                                   IN CONCLUSION  
  521.         
  522.         
  523.         The file KEYTEST.PAS demonstrates the use of the procedures 
  524.         contained in this unit.  First, it contains a loop to print the 
  525.         states of the various toggle keys (Shift, Alt, Control, Caps 
  526.         Lock, Num Lock, and Scroll Lock), which continues until a key is 
  527.         pressed.  Then, it requests a number of each type and displays 
  528.         the value returned.  Next, it asks for a string to be input, 
  529.         displays the string, and allows you to edit the string.  Finally, 
  530.         it turns on Num Lock and requests you to press a keypad key, to 
  531.         demonstrate the use of the NonNumeric variable.  The interface 
  532.         section of this unit is contained in the file KEYBOARD.INT so 
  533.         that you can see the declarations of the constants, procedures, 
  534.         and functions in the unit at a glance.  
  535.         
  536.         If you find this unit helpful in developing your own programs, a 
  537.         donation of any size would be appreciated; however, it is not 
  538.         required unless you will be using these procedures in a program 
  539.         that you will be releasing to the public; in this case, a minimum
  540.         donation of $10 is requested.
  541.  
  542.  
  543.  
  544.  
  545.  
  546.  
  547.         Please send any donations to:
  548.  
  549.                                  Tom Swingle
  550.                                  Rt.  1 Box 292
  551.                                  Waterford, OH 45786
  552.  
  553.         My college address, to reach me quicker (until June, 1992) is:
  554.  
  555.                                  Tom Swingle
  556.                                  114 Grosvenor St.
  557.                                  Athens, OH 45701
  558.  
  559.         Whether or not you send a donation, any feedback you have on this
  560.         unit is certainly welcome, and I will try to correct any bugs
  561.         reported, although I think I have tested it thoroughly enough
  562.         that almost all the bugs are gone (I have tested it quite
  563.         extensively and used it in several programs of mine).  More than
  564.         anything, I would like to hear how this unit works with unusual
  565.         setups, like computers with keystroke buffer extenders or other
  566.         such things.  I will gladly answer any questions as soon as I
  567.         can.  If you have access to electronic mail, you can reach me
  568.         faster at
  569.  
  570.           tswingle@oucsace.cs.ohiou.edu  or  swingle@duce.cs.ohiou.edu.
  571.  
  572.         This unit is still undergoing rigorous testing, and bug reports
  573.         (if any, hopefully there are none) will be coming in as time goes
  574.         by, so contact me to see if there have been any updates, if this
  575.         unit interests you!
  576.